//===============================================头文件包含======================================================//
#include "ui_bootloader.h"

#include "bsp_init.h"

#include "file_table.h"
#include "image_source.h"
#include "load_file.h"
#include "load_mv.h"
#include "log.h"

//注意在 外部磁盘根目录中不要放入过多文件, 会影响 scan_dir_files() 的扫描速度

//=================================================本地宏========================================================//

//================================================类型声明=======================================================//
enum
{
    MOVE = 0,
    FLASH,
    _LIST_MAX_
};

typedef struct
{
    disk_t disk;           //介质类型
    DIR dir;               //目录信息
    FILINFO fno;           //文件信息
    char path[FF_MAX_LFN]; //当前目录的绝对路径(注意嵌套过深会导致越界, 此处定义为 FF_MAX_LFN 一般不容易溢出, 较好的办法应是在堆中申请空间)
} list_dp_t;

typedef struct
{
    lv_obj_t *bg;

    lv_obj_t *dp_select_way;

    lv_obj_t *btn_update;
    lv_obj_t *btn_update_label;

    lv_obj_t *btn_update_all;
    lv_obj_t *btn_update_all_label;

    lv_obj_t *list[_LIST_MAX_];
    lv_obj_t *label_path[_LIST_MAX_];
    lv_obj_t *btn_next[_LIST_MAX_];
} lv_ui;

//===============================================本地变量定义======================================================//
/* 常量 */
static const char Dir_Ch = '/';               //目录标识符
static const char Dot_Ch = '.';               //文件名后缀
static const char *const Back_Str = "...";    //返回上一级目录标识符
static const uint16_t List_File_Max_Num = 10; //单页文件数量显示上限

/* 变量 */
static lv_ui Page;      //文件列表拷贝页
static lv_obj_t *MBox;  //消息弹出框
static lv_ll_t LL_File; //文件名链表

static enum {
    Default = 0,
    Option_OK,
    Option_Cancel
} Boot_Select = Default; //开机弹窗选择项,根据不同的选项进入不同的开机引导页面

//文件列表信息
static list_dp_t List_Info[_LIST_MAX_] = {
    {.disk = SD},
    {.disk = SPI}
};

//文件列表管理器跳出标志
// 0-default		
// 1-update list(对移动磁盘列表中已被点击的文件 更新拷贝至 SPI-Flash)		
// 2-updata all(在移动磁盘列表 <根目录> 下按 程序素材文件表 进行全拷贝至 SPI-Flash )
static volatile uint8_t File_Explorer_Break = 0;

//检测外置磁盘任务执行标志 < true - 执行检测任务	false-不执行 >
static volatile bool Check_Move_Media_Task_Exec = false; 

//==============================================本地函数声明======================================================//
static uint8_t plan_auto_check(void);
static uint8_t file_explorer(void);
static void boot_select_mbox_event_cb(lv_obj_t *obj, lv_event_t event);
static void tips_show_msgbox(const char *msg_text, const char *btn_text, const uint32_t wait_ms);

//================================================函数定义=======================================================//
/**
 * @brief 	启动 UI 引导加载界面, 完成 UI 文件管理及本地更新
 * @param 	\
 * @retval	0 - success     非0 - error
 * @note	\
 */
uint8_t ui_bootloader(void)
{
    uint32_t result = 0;

    //表明程序所需文件数量及显示色彩深度
    LOG_INFO("\r\n File_Table size = [%d]\r\n", File_Table.table_size);
    LOG_INFO("lvgl lcd_show a pixel = [%d]bytes \r\n", sizeof(lv_color_t));

    /* 建立映射-必备 */
    img_hash_table_init(Image_Hash_Table, sizeof(Image_Hash_Table) / sizeof(Image_Hash_Table[0]));

    //开机选择页面
    // Create a full-screen background
    lv_obj_t *obj_bg = lv_obj_create(lv_scr_act(), NULL);
    lv_obj_reset_style_list(obj_bg, LV_OBJ_PART_MAIN);
    lv_obj_set_style_local_bg_color(obj_bg, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
    // lv_obj_set_style_local_bg_opa(obj_bg, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50);
    lv_obj_set_pos(obj_bg, 0, 0);
    lv_obj_set_size(obj_bg, LV_HOR_RES_MAX, LV_VER_RES_MAX);

    const uint32_t mbox_show_ms = 6000;
    const char *btn_map[] = {"OK", "Cancel", ""};
    // Create the message box as a child of the modal background
    lv_obj_t *mbox = lv_msgbox_create(obj_bg, NULL);
    lv_msgbox_add_btns(mbox, btn_map);
    lv_msgbox_set_text(mbox, "If you want to enter the [UI DownLoad] Please click [OK], else click [Cancel]......");
    lv_obj_set_size(mbox, LV_HOR_RES_MAX / 4 * 3, LV_VER_RES_MAX / 4 * 3);
    lv_obj_align(mbox, NULL, LV_ALIGN_CENTER, 0, 0);
    lv_obj_set_event_cb(mbox, boot_select_mbox_event_cb);
    // lv_msgbox_set_anim_time(mbox, 100);
    lv_msgbox_start_auto_close(mbox, mbox_show_ms);

    //等待选择

    uint32_t prev_tick = lv_tick_get(); //标记时间戳
    for (uint32_t tick_ms = 0; Boot_Select == Default; tick_ms = lv_tick_elaps(prev_tick))
    {
        lv_task_handler();
        if (tick_ms >= mbox_show_ms) //自上一个时间戳以来经过的 ms , 超时无响应则默认选择"Option_Cancel"
        {
            Boot_Select = Option_Cancel;
            LOG_INFO("[Boot_Select] Over Time [Option_Cancel]!\r\n");
        }
    }
    lv_obj_del(obj_bg);
    obj_bg = NULL;

    //已经做出了选择!
    switch (Boot_Select)
    {
    case (Option_OK):
        result = file_explorer();
        break;

    case (Option_Cancel):
        // result = plan_auto_check();
        break;
    default:
        break;
    }
    result = plan_auto_check();
    return result;
}

/**
 * @brief	开机按计划自检
 * @param	/
 * @retval	0 - success     非0 - error
 * @note	原例程实现方式保留
 */
static uint8_t plan_auto_check(void)
{
    uint32_t result = 0;

    /* 磁盘类型
     * 磁盘是否存在
     * 磁盘内 UI 是否完整
     */
    disk_t disk_move;
    disk_move = List_Info[MOVE].disk;
    bool status_exist_move = false;
    bool status_ui_move = false;

    disk_t disk_flash;
    disk_flash = List_Info[FLASH].disk;
    bool status_exist_flash = false;
    bool status_ui_flash = false;

    enum
    {
        Check_Flash_Media = 0, //检测本地存储
        Check_Move_Media,      //检测移动存储
        Update_Media,          //更新存储介质
        Play_Boot_Video,       //播放开机视频
        _Loading_Over_         //结束
    } loading_status = Check_Flash_Media;

    while (_Loading_Over_ != loading_status)
    {
        switch (loading_status) //状态转移实现_便于后期增加Tag
        {
        case (Check_Flash_Media):
        {
            result = fs_mount(disk_flash);
            status_exist_flash = (result == 0) ? true : false;
            if (0 != result) //介质不存在(对于板载 SPI_Flash 不允许出现挂载失败的情况, 请检查硬件与底层驱动 diskio.c)
            {
                LOG_ERROR("> [%s] FATS mount is [%s]\r\n", VolumeStr[disk_flash], Debug_FR_Table[result]);
                lvgl_show_error("#FF4500 [Flash_Mem] File_System Mount error! #");
            }
            //校验文件列表
            result = check_file_table(disk_flash, &File_Table, MOVE_VIDEO_INDEX + 1, File_Table.table_size - 1);
            status_ui_flash = (result == 0) ? true : false;

            // UI 素材完整则直接跳过 检测外部介质/更新素材 环节
            loading_status = (status_ui_flash) ? Play_Boot_Video : Check_Move_Media;
            break;
        }

        case (Check_Move_Media): 
        {
            result = fs_mount(disk_move);
            status_exist_move = (result == 0) ? true : false;
            if (0 != result)
            {
                switch (result) //挂载错误枚举
                {
                case (FR_NOT_READY): // 物理设备未插入
                    LOG_ERROR("> [%s] is [%s]\r\n", VolumeStr[disk_move], Debug_FR_Table[result]);
                    lvgl_show_error("#FF4500 [Move_Mem] File_System Mount [FR_NOT_READY]! #");
                    break;

                default: //挂载异常/排查错误
                    LOG_ERROR("> [%s] FATS mount is [%s]\r\n", VolumeStr[disk_move], Debug_FR_Table[result]);
                    lvgl_show_error("#FF4500 [Move_Mem] File_System Mount error! #");
                    break;
                }
            }
            //校验文件列表
            result = check_file_table(disk_move, &File_Table, MOVE_VIDEO_INDEX, File_Table.table_size);
            status_ui_move = (result == 0) ? true : false;

            // UI 素材完整则直接进入 更新素材 环节, 否则抛出错误
            if (!status_ui_move)
            {
                LOG_ERROR("> [%s] UI is missing [%s]\r\n", VolumeStr[disk_move], File_Table.file[result - 1].name);
                lvgl_show_error("#FF4500 [Move_Mem] is missing [UI_File]! #");
            }
            loading_status = Update_Media;
            break;
        }

        case (Update_Media):
        {
			// SPI_Flash 格式化
            result = fs_mkfs(disk_flash);
            if (FR_OK != result)
            {
                LOG_ERROR("> [SPI_Flash] FATS Formatting is [%s]\r\n", Debug_FR_Table[result]);
                lvgl_show_error("#FF4500 [SPI_Flash] Formatting is error! #");
            }

            //分段拷贝测试
            result = show_copying_file_table(disk_move, disk_flash, &File_Table, FLASH_IMG_START,
                                             File_Table.table_size - FLASH_FONT_NUM - FLASH_MUSIC_NUM - FLASH_VIDEO_NUM);
            if (0 == result)
            {
                result = show_copying_file_table(disk_move, disk_flash, &File_Table,
                                                 MOVE_VIDEO_INDEX + 1, FLASH_FONT_NUM + FLASH_MUSIC_NUM + FLASH_VIDEO_NUM - 1);
            }

            if (0 == result)
                loading_status = Play_Boot_Video;
            else // UI 素材文件更新过程出错
                lvgl_show_error("#FF4500 [UI_File] OTA error!#");
            break;
        }

        case (Play_Boot_Video):
        {
            char video_path[FF_MAX_LFN] = {0};
            //除 SD 卡外，U盘 播放视频速率跟不上
            if (disk_move == SD && status_exist_move && status_ui_move)
            {
                strcpy(video_path, VolumeStr[disk_move]);
                strcat(video_path, File_Table.file[MOVE_VIDEO_INDEX].name);
            }
            else
            {
                strcpy(video_path, VolumeStr[disk_flash]);
                strcat(video_path, File_Table.file[FLASH_VIDEO_INDEX].name);
            }

            mv_task_init(video_path);              //启动播放任务
            mv_task_set_loop(0, 1);                //仅重复播放 1 次
            lcd_set_post(LAYER_1_GUI, 0, 0, 1, 1); // LAYER_1_GUI \ LAYER_2_VIDEO

            while (0 == mv_task_handler())
            {
                touch_handler();
                if (Dev_TP.status & TP_PRES_DOWN) //有触摸操作则立即主动停止视频, 快速进入主界面 UI 显示
                {
                    mv_task_over();
                    break; // break_while()
                }
            }
            loading_status = _Loading_Over_;
            break;
        }
        default:
            break;
        }
    }
    return result;
}

/**
 * @brief	开机启动引导选项事件回调
 * @param	obj 	: 对象
 * 			event 	: 事件
 * @retval	/
 * @note	/
 */
static void boot_select_mbox_event_cb(lv_obj_t *obj, lv_event_t event)
{
    if (event == LV_EVENT_VALUE_CHANGED)
    {
        switch (lv_msgbox_get_active_btn(obj)) // lv_msgbox_get_text(obj);
        {
        case (0):
            Boot_Select = Option_OK;
            break;

        case (1):
            Boot_Select = Option_Cancel;
            break;

        default:
            break;
        }
        lv_msgbox_start_auto_close(obj, 0);
    }
}

/**
 * @brief	文件过滤器
 * @param	path : 待过滤文件名
 * @retval	true - 通过过滤     false - 被滤除
 * @note	仅保留 主菜单中 产品应用子菜单下 的 全屏测试图 & Flash 内 avi 视频, 后续可调整为自定义设置
 */
static bool file_filter(const char *path)
{
    static const char *filter_list[2 + FULL_SCR_IMG_SIZE];

    static bool init_filter_list = true;
    if (init_filter_list) //仅初始化一次
    {
        for (uint32_t i = 0; i < sizeof(filter_list) / sizeof(filter_list[0]); ++i)
        {
            if (i == 0)
            {
                filter_list[i] = File_Table.file[FLASH_VIDEO_AREA_INDEX].name;
            }
            else if (i == 1)
            {
                filter_list[i] = File_Table.file[FLASH_VIDEO_INDEX].name;
            }
            else
            {
                filter_list[i] = File_Table.file[FLASH_IMG_START + FULL_SCR_IMG_START + i - 2].name; // Image_Hash_Table
            }
        }
        init_filter_list = false;
    }

    //过滤策略:文件名是否相同
    for (uint32_t i = 0; i < sizeof(filter_list) / sizeof(filter_list[0]); ++i)
    {
        if (0 == strcmp(filter_list[i], path))
            return true;
    }
    return false;
}

/**
 * @brief 	在已打开的目录下, 进行非递归扫描指定数量的文件, 并在 list 上刷新显示
 * @param 	list_data 	 : 列表源信息
 *           file_num 	 : 待扫描的文件数量
 *          list		 : 显示列表
 *          btn_event_cb : 列表子节点事件回调
 * @retval	FRESULT 	 : File_Return_Error
 * @note	注意 : 打开目录使用 f_opendir(DIR *dir, const char *path) , 关闭目录使用 f_closedir(DIR *dir) 释放
 */
static FRESULT scan_dir_files(list_dp_t *list_data, uint16_t file_num, lv_obj_t *list, lv_event_cb_t btn_event_cb)
{
    FRESULT res;
    lv_obj_t *list_btn;
    char symbol_icon[strlen(LV_SYMBOL_UPLOAD) + 1];
    //限制单次读取数量, 采用翻页机制, 以规避初始化建表时间过长,
    //缺点是单链只能顺序遍历不能返回, 且FATFS文档中描述 DIR 结构不允许用户访问, 无法标记实际节点, 需要用户在外部设立变量跟踪
    for (uint16_t i = 0; i < file_num; )
    {
        res = f_readdir(&list_data->dir, &list_data->fno); //遍历目录
        if ((res != FR_OK) || (list_data->fno.fname[0] == 0))
            break; //读取发生错误 or 读取到末尾

        if (i == 0)
        {
            //刷新当前列表
            lv_list_clean(list);
            //不保存上一次的列表点击信息
            _lv_ll_clear(&LL_File);

            //第一项为返回上级目录
            list_btn = lv_list_add_btn(list, LV_SYMBOL_LEFT, Back_Str);
            lv_btn_set_checkable(list_btn, true); //为了触发 value_change 事件
            lv_obj_set_event_cb(list_btn, btn_event_cb);
        }

        if (list_data->fno.fattrib & AM_DIR) //文件夹
        {
            memcpy(symbol_icon, LV_SYMBOL_DIRECTORY, sizeof(LV_SYMBOL_DIRECTORY));
        }
        else //文件
        {
            if (list_data == &List_Info[MOVE]) //仅对外置存储生效
            {
				if (!file_filter(list_data->fno.fname)) //过滤无关文件
					continue;
            }

            char *ret = strrchr(list_data->fno.fname, Dot_Ch); //获取文件后缀名
            if (ret != NULL)
            {
                if (strncmp(ret, ".bin", 5) == 0)
                {
                    memcpy(symbol_icon, LV_SYMBOL_FILE, sizeof(LV_SYMBOL_FILE));
                }
                else if ((strncmp(ret, ".jpg", 5) == 0) || (strncmp(ret, ".bmp", 5) == 0) ||
                         (strncmp(ret, ".png", 5) == 0) || (strncmp(ret, ".gif", 5) == 0))
                {
                    memcpy(symbol_icon, LV_SYMBOL_IMAGE, sizeof(LV_SYMBOL_IMAGE));
                }
                else if ((strncmp(ret, ".wav", 5) == 0) || (strncmp(ret, ".mp3", 5) == 0))
                {
                    memcpy(symbol_icon, LV_SYMBOL_AUDIO, sizeof(LV_SYMBOL_AUDIO));
                }
                else if ((strncmp(ret, ".avi", 5) == 0) || (strncmp(ret, ".mp4", 5) == 0))
                {
                    memcpy(symbol_icon, LV_SYMBOL_VIDEO, sizeof(LV_SYMBOL_VIDEO));
                }
                else if ((strncmp(ret, ".c", 3) == 0) || (strncmp(ret, ".h", 3) == 0) ||
                         (strncmp(ret, ".txt", 5) == 0))
                {
                    memcpy(symbol_icon, LV_SYMBOL_EDIT, sizeof(LV_SYMBOL_EDIT));
                }
                else
                {
                    memcpy(symbol_icon, LV_SYMBOL_PASTE, sizeof(LV_SYMBOL_PASTE));
                }
            }
            else
            {
                continue;
            }
        }
        //添加 btn
        list_btn = lv_list_add_btn(list, symbol_icon, list_data->fno.fname);
        lv_btn_set_checkable(list_btn, true);
        lv_obj_set_event_cb(list_btn, btn_event_cb);
		
		++i;//只有符合条件的才计入队列, 并生成对应按钮
    }
    return res;
}

/**
 * @brief	列表事件回调
 * @param	list_data 	: 列表目录/文件信息
 * 			btn 		: 列表按钮
 * 			list 		: 列表
 * 			btn_cb 		: 列表按钮事件回调
 * @retval	/
 * @note	/
 */
static void _list_event_cb(list_dp_t *list_data, lv_obj_t *btn, lv_obj_t *list, lv_event_cb_t btn_cb)
{
    enum
    {
        DEFAULT = 0,
        DIR,
        FILE
    } obj_type = DEFAULT;

    const char *btn_text = lv_list_get_btn_text(btn);
    if (strncmp(btn_text, Back_Str, strlen(Back_Str) + 1) == 0) //如果点击返回上级目录按钮，则把当前路径从最后一个'/'字符处截断
    {
        char *ret = strrchr(list_data->path, Dir_Ch);
        if (ret != NULL) //返回上一级目录有效
        {
            *ret = 0;
            obj_type = DIR;
        }
        else //当前已在根目录
        {
            return;
        }
    }
    else
    {
        //添加点击的目标路径
        sprintf(&list_data->path[strlen(list_data->path)], "/%s", btn_text);
        if (FR_OK == f_stat(list_data->path, &list_data->fno))
        {
            obj_type = (list_data->fno.fattrib & AM_DIR) ? DIR : FILE;
        }
    }

    switch (obj_type)
    {
    case (DIR):
        if (FR_OK == f_closedir(&list_data->dir)) //切换目录须提前关闭当前已打开目录
        {
            LOG_INFO("[f_closedir] and [f_opendir] = [%s]\r\n", list_data->path);
            if (FR_OK == f_opendir(&list_data->dir, list_data->path))
            {
                scan_dir_files(list_data, List_File_Max_Num, list, btn_cb);
            }
        }

        break;

    case (FILE):
        /* 保持高亮显示, 并以单链记录 */
        LOG_INFO("exec [%s] file\r\n", list_data->path);
        if (list_data == &List_Info[MOVE]) //仅对外置存储生效
        {
            char *node = NULL;
            for (node = _lv_ll_get_head(&LL_File); node != NULL; node = _lv_ll_get_next(&LL_File, node))
            {
                if (strcmp(node, list_data->path) == 0) //注意这里指针不能直接比较
                {
                    _lv_ll_remove(&LL_File, node);
                    LOG_INFO("remove [%s] file\r\n", list_data->path);
                    break;
                }
            }
            if (node == NULL)
            {
                char *ll_ptr = _lv_ll_ins_head(&LL_File);
                strcpy(ll_ptr, list_data->path);
                LOG_INFO("ins [%s] file\r\n", list_data->path);
            }
        }
        //去掉当前最后的文件名, 回归 path 目录
        char *ret = strrchr(list_data->path, Dir_Ch);
        if (ret != NULL)
        {
            *ret = 0;
        }
        break;
    default:
        break;
    }
    LOG_INFO("return path = [%s]\r\n", list_data->path);
}

static void move_list_btn_event_handler(lv_obj_t *obj, lv_event_t event)
{
    if (event == LV_EVENT_VALUE_CHANGED) // LV_EVENT_RELEASED \ LV_EVENT_CLICKED
    {
        _list_event_cb(&List_Info[MOVE], obj, Page.list[MOVE], move_list_btn_event_handler);
        //标题栏显示当前路径
        lv_label_set_text(Page.label_path[MOVE], List_Info[MOVE].path);
    }
}

static void flash_list_btn_event_handler(lv_obj_t *obj, lv_event_t event)
{
    if (event == LV_EVENT_VALUE_CHANGED) // LV_EVENT_RELEASED \ LV_EVENT_CLICKED
    {
        _list_event_cb(&List_Info[FLASH], obj, Page.list[FLASH], flash_list_btn_event_handler);
        //标题栏显示当前路径
        lv_label_set_text(Page.label_path[FLASH], List_Info[FLASH].path);
    }
}

/**
 * @brief	消息框弹出自动关闭事件
 * @param	obj 	: 对象
 * 			event 	: 事件
 * @retval	/
 * @note	/
 */
static void check_media_mbox_event_cb(lv_obj_t *obj, lv_event_t event)
{
    if (event == LV_EVENT_DELETE && obj == MBox)
    {
        // Delete the parent modal background
        lv_obj_del_async(lv_obj_get_parent(MBox));
        // happens before object is actually deleted!
        MBox = NULL;

        Check_Move_Media_Task_Exec = false;
    }
    else if (event == LV_EVENT_VALUE_CHANGED) // A button was clicked
    {
        lv_msgbox_start_auto_close(MBox, 0);

        Check_Move_Media_Task_Exec = false;
    }
}

/**
 * @brief	检测 外部移动介质 任务
 * @param	/
 * @retval	/
 * @note	/
 */
static void check_move_media_task_cb(void) //(lv_task_t *task)
{
    uint8_t result = fs_mount(List_Info[MOVE].disk);
    if (0 == result)
    {
        memset(List_Info[MOVE].path, 0, sizeof(List_Info[MOVE].path));
        strcpy(List_Info[MOVE].path, VolumeStr[List_Info[MOVE].disk]);
        result = f_opendir(&List_Info[MOVE].dir, List_Info[MOVE].path);
        if (FR_OK == result)
        {
            result = scan_dir_files(&List_Info[MOVE], List_File_Max_Num, Page.list[MOVE], move_list_btn_event_handler);
            if (result == 0)
            {
                lv_msgbox_start_auto_close(MBox, 0);
                Check_Move_Media_Task_Exec = false;
            }
        }
    }
    else
    {
        lv_list_clean(Page.list[MOVE]);
    }
}

/**
 * @brief	消息框弹出磁盘检测提示
 * @param	disk_str : 磁盘名
 * @retval	/
 * @note	/
 */
static void check_media_msgbox(const char *disk_str)
{
    // Create a full-screen background
    lv_obj_t *obj_bg = lv_obj_create(lv_scr_act(), NULL);
    lv_obj_reset_style_list(obj_bg, LV_OBJ_PART_MAIN);
    lv_obj_set_style_local_bg_color(obj_bg, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
    lv_obj_set_style_local_bg_opa(obj_bg, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50);
    lv_obj_set_pos(obj_bg, 0, 0);
    lv_obj_set_size(obj_bg, LV_HOR_RES_MAX, LV_VER_RES_MAX);

    const uint32_t mbox_show_ms = 6000;
    static const char *btn_map[] = {"Cancel", ""};
    // Create the message box as a child of the modal background
    MBox = lv_msgbox_create(obj_bg, NULL);
    lv_msgbox_add_btns(MBox, btn_map);
    lv_msgbox_set_text_fmt(MBox, "Check Media [%s]......", disk_str);
    lv_obj_set_size(MBox, LV_HOR_RES_MAX / 2, LV_VER_RES_MAX / 3 * 2);
    lv_obj_align(MBox, NULL, LV_ALIGN_CENTER, 0, 0);
    lv_obj_set_event_cb(MBox, check_media_mbox_event_cb);
    // lv_msgbox_set_anim_time(MBox, 100);
    lv_msgbox_start_auto_close(MBox, mbox_show_ms);

    Check_Move_Media_Task_Exec = true;
}

/**
 * @brief	下拉选择更新方式
 * @param	obj 	: 对象
 * 			event 	: 事件
 * @retval	/
 * @note	/
 */
static void dp_select_way_event_handler(lv_obj_t *obj, lv_event_t event)
{
    if (event == LV_EVENT_VALUE_CHANGED)
    {
        /*
        char buf[32];
        lv_dropdown_get_selected_str(obj, buf, sizeof(buf));
        LOG_INFO("Option[%d]: %s\n", 0, buf);
        */
        switch (lv_dropdown_get_selected(obj))
        {
        case (0):
            List_Info[MOVE].disk = SD;
            break;

        case (1):
            List_Info[MOVE].disk = USB;
            break;

        default:
            return ;
        }
        strcpy(List_Info[MOVE].path, VolumeStr[List_Info[MOVE].disk]);
        lv_label_set_text(Page.label_path[MOVE], List_Info[MOVE].path);
        check_media_msgbox(VolumeStr[List_Info[MOVE].disk]);
    }
}

static void btn_update_event_handler(lv_obj_t *obj, lv_event_t event)
{
    if (event == LV_EVENT_RELEASED)
    {
        File_Explorer_Break = 1;
    }
}

static void btn_update_all_event_handler(lv_obj_t *obj, lv_event_t event)
{
    if (event == LV_EVENT_RELEASED)
    {
        File_Explorer_Break = 2;
    }
}

static void btn_next_move_event_handler(lv_obj_t *obj, lv_event_t event)
{
    if (event == LV_EVENT_RELEASED)
    {
        scan_dir_files(&List_Info[MOVE], List_File_Max_Num, Page.list[MOVE], move_list_btn_event_handler);
    }
}

static void btn_next_flash_event_handler(lv_obj_t *obj, lv_event_t event)
{
    if (event == LV_EVENT_RELEASED)
    {
        scan_dir_files(&List_Info[FLASH], List_File_Max_Num, Page.list[FLASH], flash_list_btn_event_handler);
    }
}

static void events_init_screen_boot(lv_ui *ui)
{
    lv_obj_set_event_cb(ui->dp_select_way, dp_select_way_event_handler);
    lv_obj_set_event_cb(ui->btn_update, btn_update_event_handler);
    lv_obj_set_event_cb(ui->btn_update_all, btn_update_all_event_handler);
    lv_obj_set_event_cb(ui->btn_next[MOVE], btn_next_move_event_handler);
    lv_obj_set_event_cb(ui->btn_next[FLASH], btn_next_flash_event_handler);
}

static void clean_scr_page(lv_ui *ui)
{
    lv_obj_del(ui->bg);
    ui->bg = NULL;
}

static void setup_scr_page(lv_ui *ui)
{
    // Write codes cont_bg
    ui->bg = lv_cont_create(lv_scr_act(), NULL);
    lv_obj_set_pos(ui->bg, 0, 0);
    lv_obj_set_size(ui->bg, LV_HOR_RES_MAX, LV_VER_RES_MAX);
    lv_obj_set_click(ui->bg, false);
    lv_cont_set_layout(ui->bg, LV_LAYOUT_OFF);
    lv_cont_set_fit(ui->bg, LV_FIT_NONE);
    // lv_obj_set_style_local_bg_color(ui->bg, LV_CONT_MAIN, , );//白色背景

    // Write codes dp_select_way
    ui->dp_select_way = lv_dropdown_create(ui->bg, NULL);
    lv_obj_set_pos(ui->dp_select_way, LV_HOR_RES_MAX / 20, LV_VER_RES_MAX / 15);
    lv_obj_set_width(ui->dp_select_way, LV_HOR_RES_MAX / 5);
    lv_dropdown_set_max_height(ui->dp_select_way, LV_VER_RES_MAX / 10 * 3);
    lv_dropdown_set_options(ui->dp_select_way, "SD\nUSB");        //"SD\nUSB_Host\nUSB_Device\nUART"
    lv_dropdown_set_dir(ui->dp_select_way, LV_DROPDOWN_DIR_DOWN); // LV_DROPDOWN_DIR_ /LEFT/RIGHT/UP/DOWN

    // Write codes btn_update
    ui->btn_update = lv_btn_create(ui->bg, NULL);
    lv_obj_set_pos(ui->btn_update, LV_HOR_RES_MAX / 20, LV_VER_RES_MAX / 4 * 2);
    lv_obj_set_size(ui->btn_update, LV_HOR_RES_MAX / 8, LV_VER_RES_MAX / 8);
    // Write codes btn_update_label
    ui->btn_update_label = lv_label_create(ui->btn_update, NULL);
    lv_label_set_text(ui->btn_update_label, "update");
    // lv_color_make(0x00, 0x00, 0x00)
    // lv_obj_set_style_local_outline_color(ui->btn_update_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
    // lv_obj_set_style_local_border_color(ui->btn_update_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
    lv_obj_set_style_local_text_color(ui->btn_update_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
    lv_obj_set_style_local_text_font(ui->btn_update_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_montserrat_14);

    // Write codes btn_update_all
    ui->btn_update_all = lv_btn_create(ui->bg, NULL);
    lv_obj_set_pos(ui->btn_update_all, LV_HOR_RES_MAX / 20, LV_VER_RES_MAX / 4 * 3);
    lv_obj_set_size(ui->btn_update_all, LV_HOR_RES_MAX / 8, LV_VER_RES_MAX / 8);
    // Write codes btn_update_all_label
    ui->btn_update_all_label = lv_label_create(ui->btn_update_all, NULL);
    lv_label_set_text(ui->btn_update_all_label, "update all");
    // lv_obj_set_style_local_outline_color(ui->btn_update_all_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
    // lv_obj_set_style_local_border_color(ui->btn_update_all_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
    lv_obj_set_style_local_text_color(ui->btn_update_all_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
    lv_obj_set_style_local_text_font(ui->btn_update_all_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_montserrat_14);

    for (uint16_t i = 0; i < _LIST_MAX_; ++i)
    {
        // Write codes list_move
        ui->list[i] = lv_list_create(ui->bg, NULL);
        lv_obj_set_pos(ui->list[i], LV_HOR_RES_MAX / 3 * (i + 1), LV_VER_RES_MAX / 10 * 1);
        lv_obj_set_size(ui->list[i], LV_HOR_RES_MAX / 3, LV_VER_RES_MAX / 10 * 8);
        lv_list_set_anim_time(ui->list[i], 0);
        lv_list_set_edge_flash(ui->list[i], false);

        // Write codes label_path_move
        ui->label_path[i] = lv_label_create(ui->bg, NULL);
		lv_label_set_long_mode(ui->label_path[i], LV_LABEL_LONG_SROLL_CIRC);
        lv_obj_set_pos(ui->label_path[i], LV_HOR_RES_MAX / 3 * (i + 1), LV_VER_RES_MAX / 10 / 4);
        lv_obj_set_width(ui->label_path[i], LV_HOR_RES_MAX / 3);
		lv_obj_set_height(ui->label_path[i], LV_VER_RES_MAX / 10 / 2); 
        lv_label_set_text(ui->label_path[i], VolumeStr[List_Info[i].disk]); //标题栏显示当前路径
		lv_label_set_align(ui->label_path[i], LV_LABEL_ALIGN_CENTER);
        lv_obj_set_style_local_text_color(ui->label_path[i], LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLUE);
        lv_obj_set_style_local_text_font(ui->label_path[i], LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_montserrat_16);

        // Write codes btn_next_move
        ui->btn_next[i] = lv_btn_create(ui->bg, NULL);
        lv_obj_set_pos(ui->btn_next[i], LV_HOR_RES_MAX / 3 * (i + 1) + LV_HOR_RES_MAX / 9 * 1, LV_VER_RES_MAX / 10 * 9);
        lv_obj_set_size(ui->btn_next[i], LV_HOR_RES_MAX / 9, LV_VER_RES_MAX / 10);
        lv_obj_set_style_local_pattern_image(ui->btn_next[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_SYMBOL_NEXT);
    }
    // Init events for screen
    events_init_screen_boot(ui);
}

/**
 * @brief	文件管理器
 * @param	/
 * @retval	/
 * @note	/
 */
static uint8_t file_explorer(void)
{
    uint8_t result = 0;
	char msg_text[FF_MAX_LFN];
	
    //初始化文件链表/页面布局
    _lv_ll_init(&LL_File, sizeof(((list_dp_t *)0)->path));
    setup_scr_page(&Page);

    strcpy(List_Info[FLASH].path, VolumeStr[List_Info[FLASH].disk]);
    // LOG_INFO("Flash [f_opendir] = [%s]\r\n", List_Info[FLASH].path);
    result = fs_mount(List_Info[FLASH].disk);
    if (result != 0) //(对于板载 SPI_Flash 不允许出现挂载失败的情况, 请检查硬件与底层驱动 diskio.c)
    {
        LOG_ERROR("> [%s] FATS mount is [%s]\r\n", VolumeStr[List_Info[FLASH].disk], Debug_FR_Table[result]);
        lvgl_show_error("#FF4500 [Flash_Mem] File_System Mount error! #");
        return result;
    }

scan_dir:
    //打开并扫描 Flash 根目录, 呈现文件列表
    if (FR_OK == f_opendir(&List_Info[FLASH].dir, List_Info[FLASH].path))
    {
        scan_dir_files(&List_Info[FLASH], List_File_Max_Num, Page.list[FLASH], flash_list_btn_event_handler);
    }

    //打开并扫描 Move 根目录, 呈现文件列表
    strcpy(List_Info[MOVE].path, VolumeStr[List_Info[MOVE].disk]);
    // LOG_INFO("Move [f_opendir] = [%s]\r\n", List_Info[MOVE].path);
    result = fs_mount(List_Info[MOVE].disk);
    if (result == 0)
    {
        if (FR_OK == f_opendir(&List_Info[MOVE].dir, List_Info[MOVE].path))
        {
            scan_dir_files(&List_Info[MOVE], List_File_Max_Num, Page.list[MOVE], move_list_btn_event_handler);
        }
    }

    //文件管理器运行态

    uint32_t prev_tick = lv_tick_get(); //标记时间戳
    for (uint32_t tick_ms = 0; File_Explorer_Break == 0; tick_ms = lv_tick_elaps(prev_tick))
    {
        lv_task_handler();
        if (Check_Move_Media_Task_Exec && tick_ms >= 100) //自上一个时间戳以来经过的 ms
        {
            prev_tick = lv_tick_get(); //标记时间戳
            check_move_media_task_cb();
        }
    }

    //点击后对应执行
    if (File_Explorer_Break == 1)
    {
        if (!_lv_ll_is_empty(&LL_File))//非空则进行拷贝
        {
            result = show_copying_file_list(List_Info[MOVE].disk, List_Info[FLASH].disk, &LL_File);
        }
    }
    else if (File_Explorer_Break == 2)
    {
        //校验文件列表
        result = check_file_table(List_Info[MOVE].disk, &File_Table, MOVE_VIDEO_INDEX, File_Table.table_size);
        // UI 素材完整则直接进入 更新素材 环节, 否则抛出错误
        if (result == 0)
        {
			// SPI_Flash 格式化
            result = fs_mkfs(List_Info[FLASH].disk);
            if (FR_OK != result)
			{
                LOG_ERROR("> [SPI_Flash] FATS Formatting is [%s]\r\n", Debug_FR_Table[result]);
                lvgl_show_error("#FF4500 [SPI_Flash] Formatting is error! #");
			}
            //分段拷贝测试
            result = show_copying_file_table(List_Info[MOVE].disk, List_Info[FLASH].disk, &File_Table, FLASH_IMG_START,
                                             File_Table.table_size - FLASH_FONT_NUM - FLASH_MUSIC_NUM - FLASH_VIDEO_NUM);
            if (0 == result)
            {
                result = show_copying_file_table(List_Info[MOVE].disk, List_Info[FLASH].disk, &File_Table,
                                                 MOVE_VIDEO_INDEX + 1, FLASH_FONT_NUM + FLASH_MUSIC_NUM + FLASH_VIDEO_NUM - 1);
            }
        }
		else 
		{
			memset(msg_text, 0, sizeof(msg_text));
			sprintf(msg_text, "[%s] No File : [%s]", VolumeStr[List_Info[MOVE].disk], File_Table.file[result - 1].name);
			tips_show_msgbox(msg_text, "Cancel", 6000);
			result = 0;//不属于拷贝错误
		}
    }
	
	if (0 != result)//拷贝过程出现错误
	{
		memset(msg_text, 0, sizeof(msg_text));
		sprintf(msg_text, "Copy Files [%s] -> [%s] : Debug_FR_Table - [%d]", VolumeStr[List_Info[MOVE].disk], VolumeStr[List_Info[FLASH].disk], result);
		tips_show_msgbox(msg_text, "Cancel", 6000);
	}

    //校验文件列表
    result = check_file_table(List_Info[FLASH].disk, &File_Table, MOVE_VIDEO_INDEX + 1, File_Table.table_size - 1);
    if (result != 0)
    {
        File_Explorer_Break = 0;
		
		memset(msg_text, 0, sizeof(msg_text));
		sprintf(msg_text, "[%s] No File : [%s]", VolumeStr[List_Info[FLASH].disk], File_Table.file[result - 1].name);
		tips_show_msgbox(msg_text, "Cancel", 6000);
		
        goto scan_dir;//若仍然不符合正常启动条件, 返回文件列表继续拷贝
    }

    _lv_ll_clear(&LL_File);
    clean_scr_page(&Page);

    return result;
}


//消息提示框关闭标志
static bool Tips_Show = true;

/**
 * @brief	消息提示框弹出自动关闭事件
 * @param	obj 	: 对象
 * 			event 	: 事件
 * @retval	/
 * @note	/
 */
static void tips_mbox_event_cb(lv_obj_t *obj, lv_event_t event)
{
	if (event == LV_EVENT_VALUE_CHANGED) // A button was clicked
    {
        lv_msgbox_start_auto_close(obj, 0);

        Tips_Show = false;
    }
}

/**
 * @brief	弹出消息提示框
 * @param	msg_text 	: 提示文本
 * 			btn_text 	: 按钮文本(默认只有一个)
 * 			wait_ms		: 自动关闭时间
 * @retval	/
 * @note	阻塞时间为 wait_ms 
 */
static void tips_show_msgbox(const char *msg_text, const char *btn_text, const uint32_t wait_ms)
{
	//错误提示页面
	// Create a full-screen background
	lv_obj_t *obj_bg = lv_obj_create(lv_scr_act(), NULL);
	lv_obj_reset_style_list(obj_bg, LV_OBJ_PART_MAIN);
	lv_obj_set_style_local_bg_color(obj_bg, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
	lv_obj_set_style_local_bg_opa(obj_bg, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50);
	lv_obj_set_pos(obj_bg, 0, 0);
	lv_obj_set_size(obj_bg, LV_HOR_RES_MAX, LV_VER_RES_MAX);

	const uint32_t mbox_show_ms = wait_ms;
	const char *btn_map[] = {btn_text, ""};
	// Create the message box as a child of the modal background
	lv_obj_t *mbox = lv_msgbox_create(obj_bg, NULL);
	lv_msgbox_add_btns(mbox, btn_map);
	//lv_msgbox_set_recolor(mbox, true);
	lv_obj_set_style_local_text_color(mbox, LV_MSGBOX_PART_BG, LV_STATE_DEFAULT, LV_COLOR_RED);
	lv_msgbox_set_text_fmt(mbox, "%s", msg_text);
	lv_obj_set_size(mbox, LV_HOR_RES_MAX / 4 * 3, LV_VER_RES_MAX / 4 * 3);
	lv_obj_align(mbox, NULL, LV_ALIGN_CENTER, 0, 0);
	lv_obj_set_event_cb(mbox, tips_mbox_event_cb);
	// lv_msgbox_set_anim_time(mbox, 100);
	lv_msgbox_start_auto_close(mbox, mbox_show_ms);

	Tips_Show = true;
	
	//等待点击
	uint32_t prev_tick = lv_tick_get(); //标记时间戳
	for (uint32_t tick_ms = 0; Tips_Show == true; tick_ms = lv_tick_elaps(prev_tick))
	{
		lv_task_handler();
		if (tick_ms >= mbox_show_ms) //自上一个时间戳以来经过的 ms , 超时无响应则默认选择"Cancel"
		{
			Tips_Show = false;
		}
	}
	lv_obj_del(obj_bg);
	obj_bg = NULL;
}

